#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <stdarg.h>
#include "DemoDateTime.h"

// 获取当前unix 时间戳，精确到 秒
int64_t getTsInSec(void) {
    time_t currTime = time(NULL);
	return (int64_t)currTime;
}

// 获取当前unix 时间戳，精确到ms
int64_t getTsInMs(void) {
    // 获得当前时间，单位：ts.tv_nsec 纳秒
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);

    long            ms;		// Milliseconds
    //time_t          sec;	// Seconds

    int64_t msRtn = ts.tv_sec;
	msRtn *= 1000;

    ms = round(ts.tv_nsec / 1.0e6);
	msRtn += ms;

	return msRtn;
}

// 获取当前unix 时间戳，精确到 微秒
int64_t getTsInWeimiao(void) {
    // 获得当前时间，单位：ts.tv_nsec 纳秒
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);

    long            ms;		// MicroSeconds
    //time_t          sec;	// Seconds

    int64_t msRtn = ts.tv_sec;
	msRtn *= 1000000;

    ms = round(ts.tv_nsec / 1.0e3);
	msRtn += ms;

	return msRtn;
}

static int64_t __tsBegin;

void logTsBegin(const char* logFmt, ...) {
	__tsBegin = getTsInMs();
	printf("\n0ms, ");

    // 不定参数
    va_list args;
    va_start(args, logFmt);
	vprintf(logFmt, args);
    va_end(args);
}

void logTs(const char* logFmt, ...) {
	int64_t tsMs = getTsInMs();
	printf("\n%lld ms, ", tsMs - __tsBegin);

    // 不定参数
    va_list args;
    va_start(args, logFmt);
	vprintf(logFmt, args);
    va_end(args);
}

static int64_t __tsMicroBegin;

void logWsBegin(const char* logFmt, ...) {
	__tsMicroBegin = getTsInWeimiao();
	printf("\n0ws, ");

    // 不定参数
    va_list args;
    va_start(args, logFmt);
	vprintf(logFmt, args);
    va_end(args);
}

void logWs(const char* logFmt, ...) {
	int64_t tsMs = getTsInWeimiao();
	printf("\n%lld ws, ", tsMs - __tsMicroBegin);

    // 不定参数
    va_list args;
    va_start(args, logFmt);
	vprintf(logFmt, args);
    va_end(args);
}

BOOL isLittleEndian(void) {
    //大端(Big-Endian)：   高位字节放内存低地址段；低位字节放内存高地址段
    //小端(Little-Endian)：高位字节放内存高地址段；低位字节放内存低地址段
    //0x12345678 大端内存存放： 低地址 --> | 12 | 34 | 56 | 78 | --> 高地址
    //0x12345678 小端内存存放： 低地址 --> | 78 | 56 | 34 | 12 | --> 高地址

//    Windos(x86,x64)和Linux(x86,x64)都是Little Endian操作系统。
//    在ARM上，我见到的都是用Little Endian方式存储数据。
//    C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的。
//    JAVA编写的程序则唯一采用Big Endian方式来存储数据。
//    所有网络协议也都是采用Big Endian的方式来传输数据的。所以有时我们也会把Big Endian方式称之为网络字节序。
    
    int n = 1;
    return *((char *)(&n)) == 1;
}

char* strFromTimeStamp(const char* format, time_t ts) {
    if (0 == strlen(format)) {
        return NULL;
    }

    if (ts < 0) {
        ts = time(NULL);
    }
    struct tm *tInfo = localtime(&ts);
    int bufferSize = 2 * strlen(format);
    char *rtnStr = (char*)malloc(bufferSize);

/* strftime 格式说明

	%a	缩写的星期几名称			Sun
	%A	完整的星期几名称			Sunday
	%b	缩写的月份名称				Mar
	%B	完整的月份名称				March
	%c	日期和时间表示法			Sun Aug 19 02:56:02 2012
	%d	一月中的第几天（01-31）		19
	%H	24 小时格式的小时（00-23）	14
	%I	12 小时格式的小时（01-12）	05
	%j	一年中的第几天（001-366）	231
	%m	十进制数表示的月份（01-12）	08
	%M	分（00-59）					55
	%p	AM 或 PM 名称				PM
	%S	秒（00-61）					02
	%U	一年中的第几周，以第一个星期日作为第一周的第一天（00-53）	33
	%w	十进制数表示的星期几，星期日表示为 0（0-6）					4
	%W	一年中的第几周，以第一个星期一作为第一周的第一天（00-53）	34
	%x	日期表示法					08/19/12
	%X	时间表示法					02:50:06
	%y	年份，最后两个数字（00-99）	01
	%Y	年份						2012
	%Z	时区的名称或缩写			CDT
	%%	一个 % 符号	%

*/
    strftime(rtnStr, bufferSize, format, tInfo);

    return rtnStr;
}

time_t timeStampFromStr(const char* timeStr, const char* format) {
    if (0 == strlen(format) || 0 == strlen(timeStr)) {
        return -1;
    }

    // 字符串(本地时间) 转 时间结构
	struct tm tInfoFromStr;
	strptime(timeStr, format, &tInfoFromStr);

    	// 时间结构 转 时间戳
    tInfoFromStr.tm_zone = NULL;
    tInfoFromStr.tm_gmtoff = 0;
    tInfoFromStr.tm_isdst = -1;
// mktime() 将 struct tm 描述的时间转换成时间戳，tm_isdst 传入的时间是否是 DST(夏令时)，tm_isdst 含有：
//  1: 是 DST
//  0: 不是 DST
//  -1: 由 mktime() 自己去判断当前系统设置是否是 DST
	time_t tsFromStr = mktime(&tInfoFromStr);

    return tsFromStr;
}

// ts时间戳所在那天的 第几秒，ts小于零表示当前时间戳
time_t secCountInTheDay(time_t ts) {
    if (ts < 0) {
        ts = time(NULL);
    }

    char *timeStr = strFromTimeStamp("%Y-%m-%d 00:00:00", ts);
    //printf("timeStr %s\n", timeStr);

    time_t tsBgeinOfDay = timeStampFromStr(timeStr, "%Y-%m-%d %H:%M:%S");
    //printf("tsBgeinOfDay %ld\n", tsBgeinOfDay);

    return ts-tsBgeinOfDay;
}

void demoDateTimeLogic(void) {
    logWsBegin("demoDateTimeLogic begin\n");

    int64_t ts = getTsInSec();
    printf("ts %lld\n", ts);

    char *nowTimeStr = strFromTimeStamp("%Y-%m-%d %H:%M:%S", -1);
    printf("nowTimeStr %s\n", nowTimeStr);

    ts -= 10000;
    char *before10kTimeStr = strFromTimeStamp("%Y-%m-%d %H:%M:%S", ts);
    printf("before10kTimeStr %s\n", before10kTimeStr);

    time_t tsFromDate = timeStampFromStr(before10kTimeStr, "%Y-%m-%d %H:%M:%S");
    printf("tsFromDate %ld\n", tsFromDate);

    time_t secCount = secCountInTheDay(-1);
    printf("second Count from begin of today %ld\n", secCount);

    free(nowTimeStr);
    free(before10kTimeStr);

    logWs("demoDateTimeLogic end\n");
}

void demoDateTimeLogicOld(void) {
	// unix 时间戳, 精确到秒
    time_t currTime = time(NULL);

    if(currTime != (time_t)(-1)) {
        printf("The current timestamp is: %ld(s)\n", currTime);
    }

    struct tm *tInfo = localtime(&currTime);
    // struct tm {
    //     int	tm_sec;		/* seconds after the minute [0-60] */
    //     int	tm_min;		/* minutes after the hour [0-59] */
    //     int	tm_hour;	/* hours since midnight [0-23] */
    //     int	tm_mday;	/* day of the month [1-31] */
    //     int	tm_mon;		/* months since January [0-11] */
    //     int	tm_year;	/* years since 1900 */
    //     int	tm_wday;	/* days since Sunday [0-6] */
    //     int	tm_yday;	/* days since January 1 [0-365] */
    //     int	tm_isdst;	/* Daylight Savings Time flag */
    //     long	tm_gmtoff;	/* offset from UTC in seconds */
    //     char	*tm_zone;	/* timezone abbreviation */
    // };
    char dateStr[64];
    sprintf(dateStr, "%d-%d-%d %d:%d:%d zone %s UTC+%ld", 
            (1900 + tInfo->tm_year), 
			(1 + tInfo->tm_mon), 
			tInfo->tm_mday, 
            tInfo->tm_hour, 
			tInfo->tm_min, 
			tInfo->tm_sec,
            tInfo->tm_zone,
            tInfo->tm_gmtoff);
    printf("dateStr %s\n", dateStr);

/* strftime 格式说明

	%a	缩写的星期几名称			Sun
	%A	完整的星期几名称			Sunday
	%b	缩写的月份名称				Mar
	%B	完整的月份名称				March
	%c	日期和时间表示法			Sun Aug 19 02:56:02 2012
	%d	一月中的第几天（01-31）		19
	%H	24 小时格式的小时（00-23）	14
	%I	12 小时格式的小时（01-12）	05
	%j	一年中的第几天（001-366）	231
	%m	十进制数表示的月份（01-12）	08
	%M	分（00-59）					55
	%p	AM 或 PM 名称				PM
	%S	秒（00-61）					02
	%U	一年中的第几周，以第一个星期日作为第一周的第一天（00-53）	33
	%w	十进制数表示的星期几，星期日表示为 0（0-6）				  4
	%W	一年中的第几周，以第一个星期一作为第一周的第一天（00-53）	34
	%x	日期表示法					08/19/12
	%X	时间表示法					02:50:06
	%y	年份，最后两个数字（00-99）	01
	%Y	年份						2012
	%Z	时区的名称或缩写			CDT
	%%	一个 % 符号	%

*/
	strftime(dateStr, sizeof(dateStr), "%Y-%m-%d %X", tInfo);
    printf("dateStr by strftime %%Y-%%m-%%d %%X %s\n", dateStr);

	strftime(dateStr, sizeof(dateStr), "%a_%b_%d_%X_%Y", tInfo);
    printf("dateStr by strftime %%a_%%b_%%d_%%X_%%Y %s\n", dateStr);

    strftime(dateStr, sizeof(dateStr), "%Y-%m-%d %H:%M:%S", tInfo);
    printf("dateStr by strftime %%Y-%%m-%%d %%H:%%M:%%S %s\n", dateStr);
/*
dateStr by strftime %Y-%m-%d %X 2022-08-15 14:00:56
dateStr by strftime %a_%b_%d_%X_%Y Mon_Aug_15_14:00:56_2022
dateStr by strftime %Y-%m-%d %H:%M:%S 2022-08-15 14:00:56
*/

	// 字符串(本地时间) 转 时间结构
	struct tm tInfoFromStr;
	strptime(dateStr, "%Y-%m-%d %H:%M:%S", &tInfoFromStr);
    //tInfoFromStr.tm_gmtoff = tInfo->tm_gmtoff;
    //tInfoFromStr.tm_zone = tInfo->tm_zone;
    sprintf(dateStr, "%d-%d-%d %d:%d:%d isdst %d zone %s UTC+%ld", 
            (1900 + tInfoFromStr.tm_year), 
			(1 + tInfoFromStr.tm_mon), 
			tInfoFromStr.tm_mday, 
            tInfoFromStr.tm_hour, 
			tInfoFromStr.tm_min, 
			tInfoFromStr.tm_sec,
            tInfoFromStr.tm_isdst,
            tInfoFromStr.tm_zone,
            tInfoFromStr.tm_gmtoff);

    printf("dateStr by strptime %s\n", dateStr);

	// 时间结构 转 时间戳
    tInfoFromStr.tm_zone = NULL;
    tInfoFromStr.tm_gmtoff = 0;
    tInfoFromStr.tm_isdst = -1;
// mktime() 将 struct tm 描述的时间转换成时间戳，tm_isdst 传入的时间是否是 DST(夏令时)，tm_isdst 含有：
//  1: 是 DST
//  0: 不是 DST
//  -1: 由 mktime() 自己去判断当前系统设置是否是 DST
	time_t tsFromStr = mktime(&tInfoFromStr);
    
    // 从实验看，mktime是将tInfoFromStr 信息，视作本地时间来计算返回 时间戳的

    printf("tsFromStr from mktime, %ld\n", tsFromStr);
	if (tsFromStr > 0) {
		//printf("tsFromStr + tm2Getgmtoff->tm_gmtoff = %ld\n", tsFromStr);
	} else {
		printf("fail to call mktime\n");
	}

    // 获得当前时间，单位：ts.tv_nsec 纳秒
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    long            ms;		// Milliseconds
    time_t          sec;	// Seconds
    sec = ts.tv_sec;
    ms = round(ts.tv_nsec / 1.0e6);
    if (ms > 999) {
        sec += 1;
        ms -= 1000;
    }
    printf("sec %ld ms %ld\n", sec, ms);
}
